/*
 * XFrame.java, created on Oct 21, 2005
 * 
 * Copyright (c) 2005 - 2007
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.xfc.components;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.prefs.Preferences;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.WindowConstants;

import org.xfc.XApp;
import org.xfc.util.XUtils;

/**
 * 
 * 
 * @author Devon Carew
 */
public class XFrame
	extends JFrame
{
	protected int 			busyCount;
	
	protected XApp 			app;
	
	protected XGlassPane 	glassPane = new XGlassPane();
	
	private Dimension 		initialSize;
	
	/**
	 * 
	 */
	public XFrame()
	{
		setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
		
		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				getFrameCloseAction().actionPerformed(null);
			}
		});
		
		// We add a mouse listener to the glass pane so that when it is visible (when the frame is in
		// a busy state) it consumes mouse events instead of passing them through to other components.
		setGlassPane(glassPane);
		getGlassPane().addMouseListener(new MouseAdapter() { });
	}
	
	/**
	 * @param app
	 */
	public XFrame(XApp app)
	{
		this();
		
		this.app = app;
		
		app.register(this);
	}
	
	/**
	 * @return Returns the app.
	 */
	public XApp getApp()
	{
		return app;
	}
	
	/**
	 * @param app The app to set.
	 */
	public void setApp(XApp app)
	{
		this.app = app;
	}
	
	/**
	 * @return an action which when performed, will try and close this frame
	 */
	public Action getFrameCloseAction()
	{
		return new AbstractAction() {
			public void actionPerformed(ActionEvent event) {
				if (canCloseFrame())
					dispose();
			}
		};
	}
	
	/**
	 * Returns whether it is safe to close this frame. This means that there are no dirty
	 * views, or the user has explicitly OK'd closing without saving changes.
	 * 
	 * @return whether it is safe to close this frame
	 */
	public boolean canCloseFrame()
	{
		// TODO: ask if the user wants to save any dirty views -
		
		saveState(getPreferences());
		
		return true;
	}
	
	/**
	 * @param busy
	 */
	public void setBusy(boolean busy)
	{
		boolean wasBusy = isBusy();
		
		busyCount += (busy ? 1 : -1);
		
		if (busyCount < 0)
			throw new IllegalStateException("Unmatched call to setBusy(false)");
		
		if (wasBusy && !isBusy())
		{
			// Turn off the busy indicators.
			getGlassPane().setCursor(null);
			getGlassPane().setVisible(false);
		}
		else if (!wasBusy && isBusy())
		{
			// Turn on the busy indicators.
			getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
			getGlassPane().setVisible(true);
		}
	}
	
	/**
	 * @return whether this frame is busy, i.e. showing a wait cursor
	 */
	public boolean isBusy()
	{
		return busyCount > 0;
	}
	
	
	// TODO: handle interactions with busy -
	
	public void setEnabled(boolean enabled)
	{
		super.setEnabled(enabled);
		
		if (enabled)
		{
			getGlassPane().setVisible(false);
			glassPane.setDrawsBackground(false);
			repaint();
		}
		else
		{
			glassPane.setDrawsBackground(true);
			getGlassPane().setVisible(true);
			getGlassPane().repaint();
		}
	}
	
	/**
	 * @param preferences
	 */
	protected void restoreState(Preferences preferences)
	{
		// TODO: there is a lot more stuff to consider here, like maximized windows and whether
		// the window is now going to open off the screen.
		
		Rectangle bounds = XUtils.convertToRectangle(preferences.get("bounds", null));
		
		if (bounds != null)
		{
			setBounds(bounds);
		}
		else
		{
			if (getInitialSize() != null)
				setSize(getInitialSize());
			else
				pack();
			
			// TODO: locate on screen based on a reasonable first position, possibly using
			// last opened windows as a reference.
			
			
		}
		
		// TODO: child views -
		
		
	}
	
	/**
	 * @param preferences
	 */
	protected void saveState(Preferences preferences)
	{
		preferences.put("bounds", XUtils.convertToString(getBounds()));
		
		// TODO: child views -
		
		
	}
	
	public Dimension getInitialSize()
	{
		return initialSize;
	}
	
	public void setInitialSize(Dimension initialSize)
	{
		this.initialSize = initialSize;
	}
	
	public void center()
	{
		if (getApp() == null)
			XUtils.centerWindow(this);
		else
			XUtils.centerWindow(getApp().getActiveAppFrame(), this);
	}
	
	/**
	 * @return the Preferences for this XFrame
	 */
	public Preferences getPreferences()
	{
		// TODO: what if app == null? Could this even happen?
		
		// TODO: use getName() for the ID? getTitle() if getName() was never explicitely set?
		
		return getApp().getPreferences(getClass(), getTitle());
	}
	
	public void show()
	{
		restoreState(getPreferences());
		
		super.show();
	}
	
	public void hide()
	{
		saveState(getPreferences());
		
		super.hide();
		
	}

	public void dispose()
	{
		super.dispose();
		
		if (getApp() != null)
			getApp().unregister(this);
	}
	
}

class XGlassPane
	extends JComponent
{
	private boolean drawsBackground;
	
	/**
	 * 
	 */
	public XGlassPane()
	{
		setOpaque(false);
		
		setBackground(new Color(0xA0, 0xA0, 0xC0, 0x20));
	}
	
	/**
	 * @return Returns the drawsBackgroud.
	 */
	public boolean isDrawsBackground()
	{
		return drawsBackground;
	}

	/**
	 * @param drawsBackgroud The drawsBackgroud to set.
	 */
	public void setDrawsBackground(boolean drawsBackgroud)
	{
		this.drawsBackground = drawsBackgroud;
		
		repaint();
	}

	protected void paintComponent(Graphics g)
	{
		if (isDrawsBackground())
		{
			Dimension size = getSize();
			
			g.setColor(getBackground());
			g.fillRect(0, 0, size.width, size.height);
		}
	}
}
